//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <util.h>
#include <udelay.h>
#include <gpio.h>
#include <bulverde.h>

#define FAILED(x) (x != 0)
#define PAC_REG_BASE    0x40500200   /* Primary Audio Codec */
#define SAC_REG_BASE    0x40500300  /* Secondary Audio Codec */

// AC97 control registers
#define POCR        __REG( 0x40500000 )          //     PCM Out Control register
#define PCMICR      __REG( 0x40500004 )          //     PCM In Control register
#define MCCR        __REG( 0x40500008 )          //     Mic In Control register
#define GCR         __REG( 0x4050000C )          //     Global Control register
#define POSR        __REG( 0x40500010 )          //     PCM Out Status register
#define PCMISR      __REG( 0x40500014 )          //     PCM In Status register
#define MCSR        __REG( 0x40500018 )          //     Mic In Status register
#define GSR         __REG( 0x4050001C )          //     Global Status register
#define CAR         __REG( 0x40500020 )          //     CODEC Access register
#define PCDR        __REG( 0x40500040 )          //     PCM FIFO Data register
#define MCDR        __REG( 0x40500060 )          //     Mic-in FIFO Data register

// AC97 Global Control Register bit mask constants
#define XLLP_AC97_GCR_GIE_MSK          (1u << 0 )
#define XLLP_AC97_GCR_COLD_RESET_MSK   (1u << 1 )
#define XLLP_AC97_GCR_WARM_RESET_MSK   (1u << 2 )
#define XLLP_AC97_GCR_LINK_OFF_MSK     (1u << 3 )
#define XLLP_AC97_GCR_PCRSM_IEN_MSK    (1u << 4 )
#define XLLP_AC97_GCR_SCRSM_IEN_MSK    (1u << 5 )
#define XLLP_AC97_GCR_PCRDY_IEN_MSK    (1u << 8 )
#define XLLP_AC97_GCR_SCRDY_IEN_MSK    (1u << 9 )
#define XLLP_AC97_GCR_SDONE_IE_MSK     (1u << 18)
#define XLLP_AC97_GCR_CDONE_IE_MSK     (1u << 19)
#define XLLP_AC97_GCR_nDMAEN_MSK       (1u << 24)

// Global Status Register bit mask constants
#define XLLP_AC97_GSR_GSCI_MSK       (1u << 0 )
#define XLLP_AC97_GSR_MIINT_MSK      (1u << 1 )
#define XLLP_AC97_GSR_MOINT_MSK      (1u << 2 )
#define XLLP_AC97_GSR_ACOFFD_MSK     (1u << 3 )
#define XLLP_AC97_GSR_PIINT_MSK      (1u << 5 )
#define XLLP_AC97_GSR_POINT_MSK      (1u << 6 )
#define XLLP_AC97_GSR_MINT_MSK       (1u << 7 )
#define XLLP_AC97_GSR_PCRDY_MSK      (1u << 8 )
#define XLLP_AC97_GSR_SCRDY_MSK      (1u << 9 )
#define XLLP_AC97_GSR_PCRSM_MSK      (1u << 10)
#define XLLP_AC97_GSR_SCRSM_MSK      (1u << 11)
#define XLLP_AC97_GSR_SLT12_BITS_MSK (7u << 12)
#define XLLP_AC97_GSR_RCS_ERR_MSK    (1u << 15)
#define XLLP_AC97_GSR_SDONE_MSK      (1u << 18)
#define XLLP_AC97_GSR_CDONE_MSK      (1u << 19)

// Bit mask and values for CAIP bit in CAR register.
#define XLLP_AC97_CAR_CAIP_MSK       (0x1<<0)
#define XLLP_AC97_CAR_CAIP_LOCKED    (0x1<<0)
#define XLLP_AC97_CAR_CAIP_CLEAR     (0<<0)

// timeout in waiting for locking the link successfully
#define XLLP_AC97_LOCK_TIMEOUT_DEF          300 //unit is us

typedef enum
{
    XLLP_AC97_CODEC_PRIMARY = 0,
    XLLP_AC97_CODEC_SECONDARY = 1
} XLLP_AC97_CODEC_SEL_T ;

// Try to lock the AC Link for command/status accesses to a codec.
int Ac97LinkLock()
{
    unsigned int carTmp;

    carTmp = CAR;
    if (carTmp & XLLP_AC97_CAR_CAIP_MSK) {
        // "1" in CAIP bit means lock failed.
        return 1;
    }
    return 0;
}

void  Ac97Lock()
{
}

void  Ac97Unlock()
{
}


int  RawAc97Read(unsigned short offset,
                                unsigned short *pdata,
                                unsigned int maxRWTimeOutUs,
                                XLLP_AC97_CODEC_SEL_T codecSel)
{
    int status = 0;
    int gotLink;
    int timeRemaining;
    volatile unsigned int*  pCodecReg;


   if (XLLP_AC97_CODEC_PRIMARY == codecSel){
        pCodecReg = (unsigned int*)PAC_REG_BASE;
   }
   else{
        pCodecReg = (unsigned int*)SAC_REG_BASE;
   }
   pCodecReg += (offset>>1);

    //Lock the ACLINK
    timeRemaining = XLLP_AC97_LOCK_TIMEOUT_DEF;
    do {
        gotLink = Ac97LinkLock();
        if (FAILED(gotLink)) {   // 1 usec is a long time.  Skip delay if possible.
            udelay(100);
        }
    }        // Wait while time remaining and ACLINK not available
    while (timeRemaining-- && (FAILED(gotLink)));

    if (FAILED(gotLink)) {    // Didn't get the ACLINK
        status = 1;
    }
    else {   // We got the link. Perform the write operation and don't wait.
         // First, clear old read status indications.
        GSR = XLLP_AC97_GSR_SDONE_MSK | XLLP_AC97_GSR_RCS_ERR_MSK;
        //may error
        // This is THE DUMMY READ.
        *pdata = (unsigned short)(*pCodecReg);

         // Wait for read I/O with codec to complete before doing real read.
        timeRemaining = maxRWTimeOutUs;
        do {
            udelay(100);
        }   // Wait while time remaining and read I/O still incomplete
        while( (timeRemaining--) && (!(GSR & XLLP_AC97_GSR_SDONE_MSK)) );

        if ((GSR & XLLP_AC97_GSR_SDONE_MSK) && (!(GSR & XLLP_AC97_GSR_RCS_ERR_MSK)) )
        {
             // succeed in reading. clear status bits first.
            GSR = XLLP_AC97_GSR_SDONE_MSK | XLLP_AC97_GSR_RCS_ERR_MSK;
            *pdata = (unsigned short)(*pCodecReg);  // THE REAL READ.

            timeRemaining = maxRWTimeOutUs;
            do
            {
                udelay(100);
            }   // Wait while time remaining and read I/O still incomplete
            while( (timeRemaining--) && (!(GSR & XLLP_AC97_GSR_SDONE_MSK)) );
        }
        else    // failed
        {
            status = 1;
            CAR = XLLP_AC97_CAR_CAIP_CLEAR;
        } // else  (OK to do real read)
    } // else  (We got the link.  Perform the read operations.)

    return (status);
}

int  RawAc97Write(unsigned short offset,
                        unsigned short data,
                        unsigned int maxRWTimeOutUs,
                        XLLP_AC97_CODEC_SEL_T codecSel)
{
    int status = 0;
    unsigned long   gotLink;
    unsigned int    timeRemaining;
    unsigned int*   pCodecReg;

    if (XLLP_AC97_CODEC_PRIMARY == codecSel){
        pCodecReg = (unsigned int*)PAC_REG_BASE;
    }
    else{
        pCodecReg = (unsigned int*)SAC_REG_BASE;
    }
    pCodecReg += (offset>>1);

    //Lock the ACLINK
    timeRemaining = XLLP_AC97_LOCK_TIMEOUT_DEF;
    do
    {
        gotLink = Ac97LinkLock();
        if (FAILED(gotLink))    // 1 usec is a long time.  Skip delay if possible.
        {
            udelay(10*100);
        }
    }        // Wait while time remaining and ACLINK not available
    while (timeRemaining-- && (FAILED(gotLink)));

    if (FAILED(gotLink)) {  // Didn't get the ACLINK
        status = 1;
    }
    else {    // We got the link. Perform the write operation and don't wait.
        // First, clear old write status indication CDONE by writing a ONE to that bit.
        GSR = XLLP_AC97_GSR_CDONE_MSK;

        *pCodecReg = (unsigned int)data;       // Now the write!
        //*pCodecReg = (XLLP_Vunsigned int32_T)data;
        // Wait until write cycle is complete. There should be a way
        //  to do this speculatively at the beginning of the procedure.
        //  Need to discover it. Too inefficient to always wait.
        if (offset == 0) return status;
        timeRemaining = maxRWTimeOutUs;
        do {
            udelay(10*100);
        }     // Wait while time remaining and command I/O still incomplete.

        while ( (timeRemaining--) && !(GSR & XLLP_AC97_GSR_CDONE_MSK));
        if (!(GSR & XLLP_AC97_GSR_CDONE_MSK)) {
            status = 1;
        }
    }  // Got AC link

    return(status);
}

int Ac97_Read(unsigned char Offset, unsigned short * Data)
{
    int retval = 0;

    Ac97Lock();

    retval = RawAc97Read((unsigned short)Offset, (unsigned short *)Data,
                                 5000,
                                 XLLP_AC97_CODEC_PRIMARY) ;;

    Ac97Unlock();

    return(retval);
}


int  Ac97_Write(unsigned char Offset, unsigned short  Data)
{
    int retval;
    Ac97Lock();
    retval = RawAc97Write((unsigned short) Offset , (unsigned short) Data,
                                 5000,
                                 XLLP_AC97_CODEC_PRIMARY);
    Ac97Unlock();
    return retval;
}

#define GCR_COLD_RST    (1 << 1)    /* AC'97 Cold Reset (0 = active) */
#define GSR_PCR     (1 << 8)    /*  Primary Codec Ready */
int Ac97_Reset()
{
    CKEN |= 0x80000000;
    udelay(100);
    CKEN &= ~(0x80000000);
    udelay(100);

    GCR = 0;
    udelay(500);

    // Activate a cold reset
    GCR &= ~GCR_COLD_RST;
    udelay(10000);

    GCR |= GCR_COLD_RST;// | GCR_CDONE_IE | GCR_SDONE_IE ;
    udelay(1000);

    GCR |= 0x04;
    udelay(1000);

    GPSR3 |= 1 << 17;   // GPIO113, workaround to reset the code
    udelay(300);

    int retry = 3000;
    while (!(GSR & GSR_PCR) && retry--) {
        udelay(1000);
        // Sleep
    }

    if (retry <= 0) {
        printk("Ac97 control fail, GCR %x, GSR %x\n", GCR, GSR);
        return 1;
    }
    return 0;
}

#if 0
void Ac97_WarmReset()
{
    GCR &= ~XLLP_AC97_GCR_LINK_OFF_MSK;
    CKEN &= ~(0x80000000);

    GCR |= 0x04;
    udelay(30);

    CKEN |= 0x00000004;

    while (!(GSR & GSR_PCR)) {
    }
}

void Ac97_Close()
{
    unsigned short reg;
    if (GSR & XLLP_AC97_GSR_ACOFFD_MSK) {
        return;
    }
    Ac97_Write(0x44, 0x0080); // Switch to extern clock
    // Disables PLL
    Ac97_Read(0x3c, &reg);
    reg |= 0x0200;
    Ac97_Write(0x3c, reg);
    // Close ACLINK
    Ac97_Read(0x26, &reg);
    reg |= 0x3000;
    Ac97_Write(0x26, reg);

    GCR |= XLLP_AC97_GCR_LINK_OFF_MSK;
    CKEN |= 0x80000000;
    // Wait the AC-link shutdown is complete
    while (!(GSR & XLLP_AC97_GSR_ACOFFD_MSK));
    CKEN &= ~(0x80000000);
    CKEN &= ~0x00000004;
}
#endif
